/////////////////////////////////////////////////////////////////////////////
// Purpose:     Log management
//              Implementation of runtime exceptions with logging capabilities
// Author:      Jesus Gonzalez
// Modified by:
// Copyright:   (c) 2003-2013 Jesus Gonzalez <jgonzalez@gdr-sistemas.com>
// License:     BSD License
/////////////////////////////////////////////////////////////////////////////

#include "SmartLib/Log.h"

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <execinfo.h>
#include <exception>
#include <cxxabi.h>
#include <errno.h>

#include "SmartLib/FormatString.h"

#define COLOR_RESET "\x1b[0m"
#define COLOR_MAGENTA "\x1b[35m"
#define COLOR_RED "\x1b[31;1m"
#define COLOR_YELLOW "\x1b[33;1m"
#define COLOR_BLUE "\x1b[34m"
#define COLOR_GRAY "\x1b[30;1m"

static const char* get_prio_header( int prio )
{
	switch( prio )
	{
	case LOG_PRIORITY_ERROR:
		return COLOR_RED "[ERROR]" COLOR_RESET;
	case LOG_PRIORITY_WARN:
		return COLOR_YELLOW "[WARN]" COLOR_RESET;
	case LOG_PRIORITY_INFO:
		return COLOR_BLUE "[INFO]" COLOR_RESET;
	case LOG_PRIORITY_TRACE:
		return COLOR_GRAY "[TRACE]";
	case LOG_PRIORITY_DEBUG:
		return COLOR_MAGENTA "[DEBUG]" COLOR_GRAY;
	case LOG_PRIORITY_DEBUG_EXTRA:
		return COLOR_MAGENTA "[XTDBG]" COLOR_GRAY;
	case LOG_PRIORITY_ALLOC:
		return "[ALLOC]";
	default:
		return "[UNKWN]";
	}
}

static const char* get_prio_end( int prio )
{
    switch( prio )
    {
    case LOG_PRIORITY_TRACE:
    case LOG_PRIORITY_DEBUG:
    case LOG_PRIORITY_DEBUG_EXTRA:
        return COLOR_RESET;
    default:
        return "";
    }
}

int Log::g_priorityLimit = LOG_PRIORITY_ALLOC;
LogHandler::Ptr Log::g_logHandler = NULL;

#ifdef __GNUC__

static std::string simplify_function(const char* function)
{
    std::string func(function);
    uint epos = func.rfind('(');
    if( epos == std::string::npos )
    {
        return func;
    }

    uint bpos = func.rfind(' ', epos-1);
    if( bpos == std::string::npos )
    {
        bpos = 0;
    }
    else
    {
        bpos++;
    }

    return func.substr( bpos, epos-bpos );
}

#else
#define simplify_function(x) x
#endif

static void do_log_msg( int prio, const char* category, const char* function, const char* msg )
{
    if( prio > Log::getPriorityLimit() )
    {
        return;
    }

    std::string sfunc = simplify_function(function);

    bool log_to_console = true;
	if( Log::getLogHandler().IsValid() )
	{
	    log_to_console = Log::getLogHandler()->Process( prio, category, sfunc.c_str(), msg );
	}

	if( log_to_console )
	{
	    if( category == NULL )
	    {
	        printf( "%s {%s} <%s> %s%s\n", get_prio_header(prio),
	                program_invocation_short_name,
	                sfunc.c_str(), msg, get_prio_end(prio) );
	    }
	    else
	    {
            printf( "%s {%s:%s} <%s> %s%s\n", get_prio_header(prio),
                    program_invocation_short_name,
                    category, sfunc.c_str(), msg, get_prio_end(prio) );
	    }
	}
}

void Log::LogMessage( int prio, const char* category, const char* function, const char* fmt, ... )
{
    if( prio > g_priorityLimit )
    {
        return;
    }

	va_list args;
	va_start( args, fmt );
	std::string msg = vFormatString( fmt, args );
	va_end( args );

	do_log_msg( prio, category, function,msg.c_str() );
}

RuntimeError::RuntimeError( const char* category,
                            const char* function,
                            std::string msg,
                            bool log )
: runtime_error( msg ), m_category( category ? category : "" ), m_function( function ), m_logged(log)
{
    if( log )
    {
        do_log_msg( LOG_PRIORITY_ERROR, category, function, msg.c_str() );
    }
}

void RuntimeError::Log() const
{
	if( !m_logged )
	{
	    do_log_msg( LOG_PRIORITY_ERROR, m_category.c_str(), m_function.c_str(), what() );
	}
}

#ifndef UTIL_LOG_NO_TERMINATE_OVERRIDE

#ifndef UTIL_LOG_BACKTRACE_SIZE
#define UTIL_LOG_BACKTRACE_SIZE 50
#endif

static std::string demangle(const char* line)
{
	std::string buf(line);
	int fPos = buf.rfind('(');
	int e2Pos = buf.rfind(')');
	int ePos = buf.rfind('+', e2Pos);

	size_t len = ePos-fPos-1;
	int status;
	std::string symbol = buf.substr(fPos+1, len);
	char* demangled = abi::__cxa_demangle( symbol.c_str(), NULL, NULL, &status );
	if( demangled != NULL && status == 0 )
	{
		return buf.substr(0, fPos+1) + demangled + buf.substr(ePos, std::string::npos);
	}
	else
	{
		return line;
	}
}

static void _ex_terminate()
{
    static bool already_tried = false;

    Log::setLogHandler(NULL);

    try
    {
        // try once to re-throw currently active exception
        if( !already_tried )
        {
        	already_tried = true;
        	throw;
        }
    }
    catch( const RuntimeError &e )
    {
    	e.Log();
    }
    catch (const std::exception &e)
    {
        do_log_msg( LOG_PRIORITY_ERROR, NULL, typeid(e).name(), e.what() );
    }
    catch (...) {
        do_log_msg( LOG_PRIORITY_ERROR, NULL, "Unknown", "Unknown exception" );
    }

	fprintf( stderr, "Unhandled exception, program terminated\n" );

#ifndef SMART_LOG_NO_BACKTRACE

	fprintf( stderr, "Backtrace:\n" );

    void* fnAddr[UTIL_LOG_BACKTRACE_SIZE];
    int size = backtrace(fnAddr, UTIL_LOG_BACKTRACE_SIZE);

    char** fnSyms = backtrace_symbols(fnAddr, size);

    if( fnSyms != NULL )
    {
		// We skip the first call in the stack, which is the call to this
    	// function
		for (int i=1; i < size; i++)
		{
		    fprintf( stderr,"[%d]: %s\n", i, demangle(fnSyms[i]).c_str() );
		}

		free(fnSyms);
    }

#endif // SMART_LOG_NO_BACKTRACE

    abort();
}

class _REInitializer
{
public:
	_REInitializer()
	{
		std::set_terminate( _ex_terminate );
	}
};

static _REInitializer reInit;

#endif // UTIL_LOG_NO_TERMINATE_OVERRIDE
